package com.yunhe.abnormal.repository.base.impl;

import com.google.common.collect.Lists;
import com.yunhe.abnormal.domain.base.AlarmRule;
import com.yunhe.common.model.PageParam;
import com.yunhe.abnormal.repository.base.AlarmRuleSession;
import com.yunhe.common.util.Neo4jConditionUtil;
import com.yunhe.common.util.QueryUtil;
import com.yunhe.common.util.StringUtil;
import org.neo4j.ogm.session.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Repository;

import java.util.*;

/**
 * 异常规则Session实现类
 * @author liuronglei
 */
@Repository
public class AlarmRuleSessionImpl implements AlarmRuleSession {
    @Autowired
    private Session session;

    /**
     * 获得返回语句
     */
    private String getReturnCypher() {
        StringBuilder query = new StringBuilder();
        query.append(" return ar")
                .append(",(ar)-[:ALARM_SCOPE_OF]->(:AlarmScope)")
                .append(",(ar)-[:ALARM_LEVEL_OF]->(:AlarmLevel)")
                .append(",(ar)-[:ALARM_TYPE_OF]->(:AlarmType)")
                .append(",(ar)-[:ALARM_CALC_TYPE_OF]->(:AlarmCalcType)")
                .append(",(ar)-[:STATIONTYPE_OF]->(:StationType)")
                .append(",(ar)-[:STATION_OF]->(:Station)")
                .append(",(ar)-[:DEVICETYPE_OF]->(:DeviceType)")
                .append(",(ar)-[:DEVICE_OF]->(:Device)")
                .append(",(ar)-[:POINTDATATYPE_OF]->(:PointDataType)")
                .append(",(ar)-[:RELATED_POINTDATATYPE_OF]->(:PointDataType)");
        return query.toString();
    }

    @Override
    public Page<AlarmRule> findByCondition(String queryStr,
                                           List<Long> stationTypeId,
                                           List<Long> stationId,
                                           List<Long> alarmLevelId,
                                           List<Long> deviceTypeId,
                                           List<Long> alarmTypeId,
                                           Long alarmScopeId,
                                           String formula,
                                           List<Long> dtime,
                                           PageParam pageParam) {
        StringBuilder str = new StringBuilder("match(ar:AlarmRule)");
        List<String> condition = new ArrayList<>();
        Map<String, Object> params = new HashMap<>();
        if (!StringUtil.isEmpty(queryStr)) {
            condition.add("(ar.desc=~('.*'+{queryStr}+'.*') or ar.userTitle=~('.*'+{queryStr}+'.*') or toString(id(ar))=~('.*'+{queryStr}+'.*'))");
            params.put("queryStr", queryStr);
        }
        if (stationTypeId !=null && !stationTypeId.isEmpty()) {
            str.append(",(ar)-[:STATIONTYPE_OF]->(st:StationType)");
            condition.add("id(st) in {stationTypeId}");
            params.put("stationTypeId", stationTypeId);
        }
        if (stationId !=null && !stationId.isEmpty()) {
            str.append(",(ar)-[:STATION_OF]->(s:Station)");
            condition.add("id(s) in {stationId}");
            params.put("stationId", stationId);
        }
        if (alarmLevelId !=null && !alarmLevelId.isEmpty()) {
            str.append(",(ar)-[:ALARM_LEVEL_OF]->(al:AlarmLevel)");
            condition.add("id(al) in {alarmLevelId}");
            params.put("alarmLevelId", alarmLevelId);
        }
        if (deviceTypeId !=null && !deviceTypeId.isEmpty()) {
            str.append(",(ar)-[:DEVICETYPE_OF]->(dc:DeviceType)");
            condition.add("id(dc) in {deviceTypeId}");
            params.put("deviceTypeId", deviceTypeId);
        }
        if (alarmTypeId !=null && !alarmTypeId.isEmpty()) {
            str.append(",(ar)-[:ALARM_TYPE_OF]->(at:AlarmType)");
            condition.add("id(at) in {alarmTypeId}");
            params.put("alarmTypeId", alarmTypeId);
        }
        if (alarmScopeId != null) {
            str.append(",(ar)-[:ALARM_SCOPE_OF]->(as:AlarmScope)");
            condition.add("id(as)={alarmScopeId}");
            params.put("alarmScopeId", alarmScopeId);
        }
        if (dtime !=null && !dtime.isEmpty()) {
            if (dtime.get(0) != null) {
                condition.add("ar.rtime>={startRtime}");
                params.put("startRtime", dtime.get(0));
            }
            if (dtime.size() > 1 && dtime.get(1) != null) {
                condition.add("ar.rtime<={endRtime}");
                params.put("endRtime", dtime.get(1));
            }
        }
        if (!StringUtil.isEmpty(formula)) {
            condition.add("ar.formula={formula}");
            params.put("formula", formula);
        }
        StringBuilder query = new StringBuilder(str);
        query.append(Neo4jConditionUtil.getConditionStr(condition)).append(getReturnCypher())
                .append(" order by ar.rtime desc");
        if (pageParam != null) {
            PageRequest pr = PageRequest.of(QueryUtil.getPage(pageParam), QueryUtil.getSize(pageParam));
            if (QueryUtil.needPaging(pageParam)) {
                query.append(" skip {offset} limit {pageSize}");
                params.put("offset", pr.getOffset());
                params.put("pageSize", pr.getPageSize());
            }
            List<AlarmRule> results = Lists.newArrayList(session.query(AlarmRule.class, query.toString(), params));
            StringBuilder count = new StringBuilder(str);
            count.append(Neo4jConditionUtil.getConditionStr(condition)).append(" return count(id(ar)) as count");
            List<Long> countResult = Lists.newArrayList(session.query(Long.class, count.toString(), params));
            Optional<Long> first = countResult.stream().findFirst();
            return new PageImpl<>(results, pr, first.orElse(0L));
        } else {
            List<AlarmRule> results = Lists.newArrayList(session.query(AlarmRule.class, query.toString(), params));
            return new PageImpl<>(results, PageRequest.of(0, Integer.MAX_VALUE), results.size());
        }
    }

    @Override
    public void updateAlarmLevel(List<Long> ids, Long alarmLevelId) {
        String query = "match(ar:AlarmRule)-[r:ALARM_LEVEL_OF]->(:AlarmLevel),(al:AlarmLevel) where id(al)={alarmLevelId} and id(ar) in {ids}" +
                " merge (ar)-[:ALARM_LEVEL_OF]->(al) delete r";
        Map<String, Object> params = new HashMap<>();
        params.put("ids", ids);
        params.put("alarmLevelId", alarmLevelId);
        session.query(query, params);
    }

    @Override
    public void unBindRelat(Long alarmRuleId) {
        String query = "match(ar:AlarmRule)-[r]->(n) where id(ar)={alarmRuleId} delete r";
        Map<String, Object> params = new HashMap<>();
        params.put("alarmRuleId", alarmRuleId);
        session.query(query, params);
    }

    @Override
    public void bindRelat(AlarmRule alarmRule) {
        List<String> match = new ArrayList<>();
        List<String> condition = new ArrayList<>();
        List<String> create = new ArrayList<>();
        Map<String, Object> params = new HashMap<>();
        params.put("alarmRuleId", alarmRule.getId());
        if (alarmRule.getAlarmType() != null) {
            match.add("(at:AlarmType)");
            condition.add("id(at)={alarmTypeId}");
            create.add("(ar)-[:ALARM_TYPE_OF]->(at)");
            params.put("alarmTypeId", alarmRule.getAlarmType().getId());
        }
        if (alarmRule.getAlarmScope() != null) {
            match.add("(as:AlarmScope)");
            condition.add("id(as)={alarmScopeId}");
            create.add("(ar)-[:ALARM_SCOPE_OF]->(as)");
            params.put("alarmScopeId", alarmRule.getAlarmScope().getId());
        }
        if (alarmRule.getAlarmLevel() != null) {
            match.add("(al:AlarmLevel)");
            condition.add("id(al)={alarmLevelId}");
            create.add("(ar)-[:ALARM_LEVEL_OF]->(al)");
            params.put("alarmLevelId", alarmRule.getAlarmLevel().getId());
        }
        if (alarmRule.getAlarmCalcType() != null) {
            match.add("(act:AlarmCalcType)");
            condition.add("id(act)={alarmCalcTypeId}");
            create.add("(ar)-[:ALARM_CALC_TYPE_OF]->(act)");
            params.put("alarmCalcTypeId", alarmRule.getAlarmCalcType().getId());
        }
        if (alarmRule.getDeviceType() != null) {
            match.add("(dc:DeviceType)");
            condition.add("id(dc)={deviceTypeId}");
            create.add("(ar)-[:DEVICETYPE_OF]->(dc)");
            params.put("deviceTypeId", alarmRule.getDeviceType().getId());
        }
        if (alarmRule.getDevices() != null) {
            List<Long> deviceId = new ArrayList<>();
            alarmRule.getDevices().forEach(item -> deviceId.add(item.getId()));
            match.add("(d:Device)");
            condition.add("id(d) in {deviceId}");
            create.add("(ar)-[:DEVICE_OF]->(d)");
            params.put("deviceId", deviceId);
        }
        if (alarmRule.getStationType() != null) {
            match.add("(st:StationType)");
            condition.add("id(st)={stationTypeId}");
            create.add("(ar)-[:STATIONTYPE_OF]->(st)");
            params.put("stationTypeId", alarmRule.getStationType().getId());
        }
        if (alarmRule.getStations() != null) {
            List<Long> stationId = new ArrayList<>();
            alarmRule.getStations().forEach(item -> stationId.add(item.getId()));
            match.add("(s:Station)");
            condition.add("id(s) in {stationId}");
            create.add("(ar)-[:STATION_OF]->(s)");
            params.put("stationId", stationId);
        }
        if (alarmRule.getRelatedPointDataTypes() != null) {
            List<Long> relatedPointDataTypeId = new ArrayList<>();
            alarmRule.getRelatedPointDataTypes().forEach(item -> relatedPointDataTypeId.add(item.getId()));
            match.add("(rpdt:PointDataType)");
            condition.add("id(rpdt) in {relatedPointDataTypeId}");
            create.add("(ar)-[:RELATED_POINTDATATYPE_OF]->(rpdt)");
            params.put("relatedPointDataTypeId", relatedPointDataTypeId);
        }
        if (alarmRule.getPointDataTypes() != null) {
            List<Long> pointDataTypeId = new ArrayList<>();
            alarmRule.getPointDataTypes().forEach(item -> pointDataTypeId.add(item.getId()));
            match.add("(pdt:PointDataType)");
            condition.add("id(pdt) in {pointDataTypeId}");
            create.add("(ar)-[:POINTDATATYPE_OF]->(pdt)");
            params.put("pointDataTypeId", pointDataTypeId);
        }
        for (int i = 0; i < match.size(); i++) {
            String query = "match(ar:AlarmRule)," + match.get(i) + " where id(ar)={alarmRuleId} and " + condition.get(i) +
                    " create " + create.get(i);
            session.query(query, params);
        }
    }

    @Override
    public List<AlarmRule> noStationBind(Long stationTypeId,
                                         Long alarmScopeId) {
        StringBuilder str = new StringBuilder("match(ar:AlarmRule)");
        List<String> condition = new ArrayList<>();
        Map<String, Object> params = new HashMap<>();
        if (stationTypeId != null) {
            str.append(",(ar)-[:STATIONTYPE_OF]->(st:StationType)");
            condition.add("id(st)={stationTypeId}");
            params.put("stationTypeId", stationTypeId);
        }
        if (alarmScopeId != null) {
            str.append(",(ar)-[:ALARM_SCOPE_OF]->(as:AlarmScope)");
            condition.add("id(as)={alarmScopeId}");
            params.put("alarmScopeId", alarmScopeId);
        }
        condition.add("not (ar)-[:STATION_OF]->(:Station)");
        StringBuilder query = new StringBuilder(str);
        query.append(Neo4jConditionUtil.getConditionStr(condition)).append(getReturnCypher());
        return Lists.newArrayList(session.query(AlarmRule.class, query.toString(), params));
    }

    @Override
    public List<AlarmRule> getStationBind(Long stationId) {
        StringBuilder query = new StringBuilder("match(ar:AlarmRule)-[:STATION_OF]->(s:Station) where id(s)={stationId}");
        Map<String, Object> params = new HashMap<>();
        params.put("stationId", stationId);
        query.append(getReturnCypher());
        return Lists.newArrayList(session.query(AlarmRule.class, query.toString(), params));
    }

    @Override
    public List<AlarmRule> noDeviceBind(Long deviceTypeId,
                                         Long alarmScopeId) {
        StringBuilder str = new StringBuilder("match(ar:AlarmRule)");
        List<String> condition = new ArrayList<>();
        Map<String, Object> params = new HashMap<>();
        if (deviceTypeId != null) {
            str.append(",(ar)-[:DEVICETYPE_OF]->(dc:DeviceType)");
            condition.add("id(dc)={deviceTypeId}");
            params.put("deviceTypeId", deviceTypeId);
        }
        if (alarmScopeId != null) {
            str.append(",(ar)-[:ALARM_SCOPE_OF]->(as:AlarmScope)");
            condition.add("id(as)={alarmScopeId}");
            params.put("alarmScopeId", alarmScopeId);
        }
        condition.add("not (ar)-[:DEVICE_OF]->(:Device)");
        StringBuilder query = new StringBuilder(str);
        query.append(Neo4jConditionUtil.getConditionStr(condition)).append(getReturnCypher());
        return Lists.newArrayList(session.query(AlarmRule.class, query.toString(), params));
    }

    @Override
    public List<AlarmRule> getDeviceBind(Long deviceId) {
        StringBuilder query = new StringBuilder("match(ar:AlarmRule)-[:DEVICE_OF]->(d:Device) where id(d)={deviceId}");
        Map<String, Object> params = new HashMap<>();
        params.put("deviceId", deviceId);
        query.append(getReturnCypher());
        return Lists.newArrayList(session.query(AlarmRule.class, query.toString(), params));
    }
}
